package com.macro.mall.security.lock;

import io.micrometer.core.instrument.util.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Collections;

@Aspect
@Component
public class LockCheckAspect {

    private static final String RELEASE_LOCK_LUA_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

    @Autowired
    private RedisTemplate redisTemplate;

    // 增强带有CacheLock注解的方法
    @Pointcut("@annotation(com.macro.mall.security.lock.CacheLock)")
    public void pointCut(){}


    @Around("pointCut()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable{

        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        CacheLock cacheLock = method.getAnnotation(CacheLock.class);
        String prefix = cacheLock.prefix();
        if (StringUtils.isBlank(prefix)){
            throw new Exception("CacheLock prefix can't be null");
        }
        // 拼接 key
        String delimiter = cacheLock.delimiter();
        StringBuilder sb = new StringBuilder();
        sb.append(prefix).append(delimiter).append(cacheLock.key());
        final String lockKey = sb.toString();
        final String UUID = cn.hutool.core.lang.UUID.fastUUID().toString();

        Integer timeout=cacheLock.expire();
        try {

            long begin = System.currentTimeMillis();
            boolean success=false;
            while(System.currentTimeMillis() - begin < timeout) {
                success = redisTemplate.opsForValue().setIfAbsent(lockKey,UUID,cacheLock.expire(),cacheLock.timeUnit());
                if(success){
                    break;
                }
                synchronized(Thread.currentThread()) {
                    Thread.currentThread().wait(300);
                }
            }
            // 获取锁
            Object result= joinPoint.proceed();
            return result;


        }finally {
            // 最后记得释放锁
            DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(RELEASE_LOCK_LUA_SCRIPT,Long.class);
            redisTemplate.execute(redisScript, Collections.singletonList(lockKey),UUID);
        }

    }


}
